﻿// DumpHelper.cpp : 定义静态库的函数。
//

#include "pch.h" // Standard VS precompiled header
#include "framework.h" // Standard VS framework header
#include "DumpHelper.h"

#include <Windows.h>
#include <DbgHelp.h>
#include <cstdio>     // For sprintf_s, printf, fprintf, stderr
#include <vector>
#include <string>
#include <algorithm>  // For std::sort
#include <ctime>      // For time, localtime_s
#include <io.h>       // For _access, _findfirst, _findnext, _findclose
#include <direct.h>   // For _mkdir
#include <sys/stat.h> // For struct stat (though not directly used)

#pragma comment(lib, "Dbghelp.lib") // Link against DbgHelp library

// Define platform-specific macros if not already done
#ifndef MAX_PATH_LEN
#define MAX_PATH_LEN 260 // Use Windows MAX_PATH definition
#endif

#ifdef _WIN32
#define ACCESS(fileName, accessMode) _access(fileName, accessMode)
#define MKDIR(path) _mkdir(path)
#else
// Basic POSIX definitions (won't fully work without more porting effort)
#define ACCESS(fileName, accessMode) access(fileName, accessMode)
#define MKDIR(path) mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
#endif

namespace AC {

	// Static variables within the namespace
	static std::string g_dumpDirectory = ".\\";
	static int g_maxDumpFileCount = 10;
	static DumpHelper::DumpMode g_currentDumpMode = DumpHelper::DumpMode::Minimal;
	static bool g_isDumpCrashHandlerSet = false;
	static LPTOP_LEVEL_EXCEPTION_FILTER g_previousExceptionFilter = nullptr;
	static bool g_userWantsCrashHandling = true;
	// --- Helper Functions ---

	// Get all files (not directories) in a given folder path
	static void GetFolderFiles(const std::string& path, std::vector<std::string>& files)
	{
		files.clear();
		intptr_t hFile = 0;
		struct _finddata_t fileinfo;
		std::string searchPath = path;
		if (!path.empty() && path.back() != '\\' && path.back() != '/') {
			searchPath += '\\';
		}
		searchPath += '*';

		if ((hFile = _findfirst(searchPath.c_str(), &fileinfo)) != -1)
		{
			do
			{
				if (strcmp(fileinfo.name, ".") == 0 || strcmp(fileinfo.name, "..") == 0) {
					continue;
				}
				if (!(fileinfo.attrib & _A_SUBDIR))
				{
					std::string filePath = path;
					if (!path.empty() && path.back() != '\\' && path.back() != '/') {
						filePath += '\\';
					}
					filePath += fileinfo.name;
					files.push_back(filePath);
				}
			} while (_findnext(hFile, &fileinfo) == 0);
			_findclose(hFile);
		}
	}

	// Create directory hierarchy
	static bool CreateMultiLevelDirectory(const std::string& directoryPath)
	{
		std::string path = directoryPath;
		if (path.empty()) return false; // Cannot create empty path

		// Ensure path ends with a separator for loop consistency, unless it's just "C:"
		if (path.back() != '\\' && path.back() != '/' && !(path.length() == 2 && path[1] == ':')) {
			path += '\\';
		}

		size_t len = path.length();
		if (len > MAX_PATH_LEN) {
			fprintf(stderr, "Error: Directory path too long: %s\n", directoryPath.c_str());
			return false;
		}

		for (size_t i = 0; i < len; ++i)
		{
			if (path[i] == '\\' || path[i] == '/')
			{
				// Skip root drive letter separator (e.g., after "C:")
				if (i == 0 || (i == 2 && path[1] == ':')) {
					continue;
				}

				std::string currentPath = path.substr(0, i);
				if (ACCESS(currentPath.c_str(), 0) != 0)
				{
					if (MKDIR(currentPath.c_str()) != 0)
					{
						// Check if error is because it already exists (EEXIST) or other permissions issue
						DWORD lastError = GetLastError(); // Check Windows error if MKDIR failed
						// EEXIST is not a standard GetLastError code, _mkdir returns -1 on failure.
						// We just check existence again.
						if (ACCESS(currentPath.c_str(), 0) != 0) {
							fprintf(stderr, "Error: Failed to create directory: %s (Error code might be relevant)\n", currentPath.c_str());
							return false;
						}
					}
				}
			}
		}
		// Final check for the complete path (handles cases where path doesn't end in \)
		if (ACCESS(directoryPath.c_str(), 0) != 0) {
			if (MKDIR(directoryPath.c_str()) != 0) {
				if (ACCESS(directoryPath.c_str(), 0) != 0) {
					fprintf(stderr, "Error: Failed to create final directory: %s\n", directoryPath.c_str());
					return false;
				}
			}
		}

		return true;
	}

	// Generate the minidump file
	// This function should not contain C++ objects requiring complex unwinding if called from __except
	static int GenerateDump(EXCEPTION_POINTERS* pExceptionInfo, const char* filePathCStr, DumpHelper::DumpMode mode)
	{
		MINIDUMP_TYPE dumpType;
		const char* dumpModeStr = "Unknown";

		// Select dump type flags based on the requested mode
		switch (mode)
		{
		case DumpHelper::DumpMode::Full:
			dumpType = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules | MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory);
			dumpModeStr = "Full";
			break;
		case DumpHelper::DumpMode::Minimal:
		default:
			dumpType = (MINIDUMP_TYPE)(MiniDumpNormal
				| MiniDumpWithUnloadedModules
				// | MiniDumpWithExceptionInfo // Removed for SDK compatibility
				);
			dumpModeStr = "Minimal";
			break;
		}
		printf("Attempting to generate %s dump file: %s\n", dumpModeStr, filePathCStr);

		// Create the dump file
		HANDLE hFile = ::CreateFileA(filePathCStr, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE)
		{
			DWORD error = GetLastError();
			fprintf(stderr, "Error: Failed to create dump file '%s'. Error code: %lu\n", filePathCStr, error);
			return EXCEPTION_CONTINUE_SEARCH; // Indicate failure
		}

		MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
		exceptionInfo.ThreadId = GetCurrentThreadId();
		exceptionInfo.ExceptionPointers = pExceptionInfo;
		exceptionInfo.ClientPointers = TRUE;

		// Write the dump
		BOOL success = MiniDumpWriteDump(
			GetCurrentProcess(),
			GetCurrentProcessId(),
			hFile,
			dumpType,
			(pExceptionInfo != nullptr) ? &exceptionInfo : nullptr,
			nullptr,
			nullptr
		);

		CloseHandle(hFile);

		if (!success)
		{
			DWORD error = GetLastError();
			fprintf(stderr, "Error: MiniDumpWriteDump failed for '%s'. Error code: %lu\n", filePathCStr, error);
		}
		else {
			printf("Dump file generated successfully: %s\n", filePathCStr);
		}

		return EXCEPTION_EXECUTE_HANDLER; // Indicate that we handled the exception (or attempted to)
	}


	// Helper function to trigger exception and call GenerateDump within SEH context
	// This function MUST NOT have C++ objects requiring unwinding in its scope
	static int GenerateSnapshotViaSEH(const char* filePathCStr, DumpHelper::DumpMode mode)
	{
		int result = EXCEPTION_CONTINUE_SEARCH; // Default to failure
		__try
		{
			RaiseException(0xE000AC01, EXCEPTION_NONCONTINUABLE, 0, NULL);
		}
		__except (result = GenerateDump(GetExceptionInformation(), filePathCStr, mode)) // Assign result here
		{
			// This block executes after GenerateDump
			// 'result' now holds the return value of GenerateDump
		}
		return result; // Return the result from GenerateDump
	}


	// Top-level exception filter function
	static LONG WINAPI UnhandledExceptionFilterCallback(LPEXCEPTION_POINTERS pExceptionInfo)
	{
		printf("Unhandled exception caught. Generating crash dump...\n");

		// Ensure using the potentially updated g_dumpDirectory
		std::string currentDumpDir = g_dumpDirectory; // Copy to local variable

		// 1. Create the dump directory if it doesn't exist
		if (!CreateMultiLevelDirectory(currentDumpDir))
		{
			fprintf(stderr, "Warning: Failed to create dump directory '%s'. Attempting to dump in current directory.\n", currentDumpDir.c_str());
			currentDumpDir = ".\\"; // Fallback to current directory for this dump attempt
			if (currentDumpDir.back() != '\\') currentDumpDir += '\\'; // Ensure trailing slash
		}
		else {
			// Ensure trailing slash if creation succeeded
			if (!currentDumpDir.empty() && currentDumpDir.back() != '\\' && currentDumpDir.back() != '/') {
				currentDumpDir += '\\';
			}
		}


		// 2. Clean up old dump files if necessary
		if (g_maxDumpFileCount > 0) {
			std::vector<std::string> filesInDir;
			// Use the potentially corrected currentDumpDir (without trailing slash for GetFolderFiles if needed by its impl)
			std::string dirForListing = currentDumpDir;
			if (!dirForListing.empty() && (dirForListing.back() == '\\' || dirForListing.back() == '/')) {
				dirForListing.pop_back();
			}
			GetFolderFiles(dirForListing, filesInDir);

			std::vector<std::string> dmpFiles;
			char ext[_MAX_EXT];
			for (const auto& file : filesInDir) {
				errno_t err = _splitpath_s(file.c_str(), NULL, 0, NULL, 0, NULL, 0, ext, _MAX_EXT);
				if (err == 0 && _stricmp(ext, ".dmp") == 0) {
					dmpFiles.push_back(file);
				}
			}

			if (dmpFiles.size() >= static_cast<size_t>(g_maxDumpFileCount)) {
				std::sort(dmpFiles.begin(), dmpFiles.end());
				int filesToDelete = (int)dmpFiles.size() - g_maxDumpFileCount + 1;
				for (int i = 0; i < filesToDelete; ++i) {
					if (!DeleteFileA(dmpFiles[i].c_str())) {
						fprintf(stderr, "Warning: Failed to delete old dump file '%s'. Error: %lu\n", dmpFiles[i].c_str(), GetLastError());
					}
					else {
						printf("Deleted old dump file: %s\n", dmpFiles[i].c_str());
					}
				}
			}
		}

		// 3. Generate the filename with timestamp
		time_t currentTime; // Corrected variable name
		time(&currentTime); // Get current time
		struct tm currentLocalTime; // Corrected variable name
		localtime_s(&currentLocalTime, &currentTime); // Use safer localtime_s

		char fileNameCStr[MAX_PATH_LEN];
		sprintf_s(fileNameCStr, MAX_PATH_LEN, "%sCrashDump_[%04d-%02d-%02d_%02d-%02d-%02d].dmp",
			currentDumpDir.c_str(), // Use the local variable with guaranteed trailing slash
			1900 + currentLocalTime.tm_year,
			currentLocalTime.tm_mon + 1,
			currentLocalTime.tm_mday,
			currentLocalTime.tm_hour,
			currentLocalTime.tm_min,
			currentLocalTime.tm_sec);

		// 4. Generate the dump using the current global mode setting
		// Pass C-style string to avoid std::string object in potentially sensitive context
		int result = GenerateDump(pExceptionInfo, fileNameCStr, g_currentDumpMode);

		// 5. Optional: Call previous filter
		// if (g_previousExceptionFilter) { return g_previousExceptionFilter(pExceptionInfo); }

		return result; // Typically EXCEPTION_EXECUTE_HANDLER from GenerateDump
	}


	// --- Public Static Methods Implementation ---

	void DumpHelper::SetIsDumpCrash(bool value)
	{
		g_userWantsCrashHandling = value; // <-- 新增：记录用户的意图

		if (value) {
			if (!g_isDumpCrashHandlerSet) {
				// Store the previous filter (if any) before setting ours
				g_previousExceptionFilter = SetUnhandledExceptionFilter(UnhandledExceptionFilterCallback);
				g_isDumpCrashHandlerSet = true;
				printf("Crash dump handler registered.\n");
			}
		}
		else {
			if (g_isDumpCrashHandlerSet) {
				// Restore the previous filter
				SetUnhandledExceptionFilter(g_previousExceptionFilter);
				g_previousExceptionFilter = nullptr; // Clear stored filter
				g_isDumpCrashHandlerSet = false;
				printf("Crash dump handler unregistered.\n");
			}
		}
	}

	void DumpHelper::SetDumpDirectory(const std::string& directory)
	{
		if (directory.empty()) {
			g_dumpDirectory = ".\\";
			printf("Dump directory set to current working directory.\n");
			return;
		}

		std::string finalDir = directory;
		bool isAbsolute = (finalDir.length() > 1 && finalDir[1] == ':') || (!finalDir.empty() && (finalDir[0] == '/' || finalDir[0] == '\\'));

		if (!isAbsolute) {
			char exePath[MAX_PATH_LEN] = { 0 };
			if (GetModuleFileNameA(NULL, exePath, MAX_PATH_LEN) > 0) {
				std::string exeFullPath = exePath;
				size_t pos = exeFullPath.find_last_of("\\/");
				std::string exeDirectory = (pos != std::string::npos) ? exeFullPath.substr(0, pos + 1) : ".\\";
				finalDir = exeDirectory + finalDir;
			}
			else {
				char currentDir[MAX_PATH_LEN] = { 0 };
				if (_getcwd(currentDir, MAX_PATH_LEN)) {
					std::string cwd = currentDir;
					if (cwd.back() != '\\') cwd += '\\';
					finalDir = cwd + finalDir;
				}
			}
		}

		// Ensure the directory path ends with a separator
		if (!finalDir.empty() && finalDir.back() != '\\' && finalDir.back() != '/') {
			finalDir.push_back('\\');
		}

		g_dumpDirectory = finalDir;
		printf("Dump directory set to: %s\n", g_dumpDirectory.c_str());

		if (!CreateMultiLevelDirectory(g_dumpDirectory)) {
			fprintf(stderr, "Warning: Failed to create dump directory upon setting: %s\n", g_dumpDirectory.c_str());
		}
	}

	void DumpHelper::SetDumpMaxFileCount(int count)
	{
		g_maxDumpFileCount = (count >= 0) ? count : 0;
		printf("Maximum dump file count set to: %d\n", g_maxDumpFileCount);
	}

	void DumpHelper::SetDumpMode(DumpHelper::DumpMode mode)
	{
		g_currentDumpMode = mode;
		printf("Global dump mode set to: %s\n", (mode == DumpMode::Full) ? "Full" : "Minimal");
	}

	// Snapshot implementation (specific mode) - Calls the SEH helper
	void DumpHelper::Snapshot(const std::string& path, DumpHelper::DumpMode mode)
	{
		printf("Generating snapshot (%s mode) to: %s\n", (mode == DumpMode::Full) ? "Full" : "Minimal", path.c_str());

		// Ensure directory for the snapshot exists if a path is included
		size_t lastSeparator = path.find_last_of("/\\");
		if (lastSeparator != std::string::npos) {
			std::string dirPath = path.substr(0, lastSeparator);
			if (!CreateMultiLevelDirectory(dirPath)) {
				fprintf(stderr, "Warning: Failed to create directory for snapshot: %s\n", dirPath.c_str());
				// Continue anyway, CreateFileA might still work or fail predictably
			}
		}

		// Call the helper function that contains the __try/__except block
		GenerateSnapshotViaSEH(path.c_str(), mode); // Pass C-string
	}

	// Snapshot implementation (uses global mode) - Calls the SEH helper
	void DumpHelper::Snapshot(const std::string& path)
	{
		printf("Generating snapshot (global mode) to: %s\n", path.c_str());

		// Ensure directory for the snapshot exists
		size_t lastSeparator = path.find_last_of("/\\");
		if (lastSeparator != std::string::npos) {
			std::string dirPath = path.substr(0, lastSeparator);
			if (!CreateMultiLevelDirectory(dirPath)) {
				fprintf(stderr, "Warning: Failed to create directory for snapshot: %s\n", dirPath.c_str());
			}
		}

		// Call the helper function that contains the __try/__except block
		GenerateSnapshotViaSEH(path.c_str(), g_currentDumpMode); // Pass C-string and global mode
	}

	void DumpHelper::Start()
	{
		printf("Initializing DumpHelper...\n");
		// 1. Set default directory ONLY if it hasn't been set yet
		if (g_dumpDirectory == ".\\") {
			SetDumpDirectory("dmp"); // Sets relative "dmp" subdirectory and prints message
		}
		// else: User already called SetDumpDirectory, keep their setting.

		// 2. Register the exception filter ONLY if the user wants it
		if (g_userWantsCrashHandling) { // <-- 修改：检查用户的意图
			SetIsDumpCrash(true); // Call the function to ensure it's set (if not already)
		}
		else {
			// Optional: Ensure it's definitely off if user explicitly set false before Start
			SetIsDumpCrash(false);
		}
	}

} // namespace AC